package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"reflect"
	"strconv"
	"strings"
)

type MysqlConfig struct {
	Address  string `ini:"address"`
	Port     int    `ini:"port"`
	Username string `ini:"username"`
	Password string `ini:"password"`
}

type RedisConfig struct {
	Host     string `ini:"host"`
	Port     int    `ini:"port"`
	Password string `ini:"password"`
	Database string `ini:"database"`
}

type Config struct {
	MysqlConfig `ini:"mysql"`
	RedisConfig `ini:"redis"`
}

func loadIni(fileName string, data interface{}) (err error) {
	//0.参数的校验
	//0.1传递进来的data参数必须是指针类型，因为需要在函数中对其赋值
	t := reflect.TypeOf(data)
	fmt.Println("t==", t, t.Kind())
	if t.Kind() != reflect.Ptr {
		err1 := errors.New("data should be a pointer") //新创建一个错误
		return err1
	}
	//0.2传递进来的data必须会结构体类型，因为配置文件中有各种键值对需要赋值给结构体的字段
	var c Config
	fmt.Printf("%T\n", c)
	fmt.Println("t.Elem()==", t.Elem(), t.Elem().Kind())
	if t.Elem().Kind() != reflect.Struct {
		err2 := errors.New("data should be a struct pointe") //新创建一个错误
		return err2
	}
	//1.读文件得到字节类型数据
	b, err3 := ioutil.ReadFile(fileName)
	if err3 != nil {
		fmt.Printf("load ini failed,err3:%v\n", err3)
		return err3
	}
	//string(b) //将字节类型的文件内容转成字符串
	lineSlice := strings.Split(string(b), "\r\n")
	fmt.Printf("%#v \n", lineSlice)
	//2.一行一行的读数据
	var structName string
	for idx, line := range lineSlice {
		//去掉字符串的首尾空格
		line = strings.TrimSpace(line)
		//如果是空行，就跳过
		if len(line) == 0 {
			continue
		}
		//2.1如果是注释就跳过
		if strings.HasPrefix(line, ";") || strings.HasPrefix(line, "#") {
			continue
		}
		//2.2如果是方括号开头表示是节
		if strings.HasPrefix(line, "[") {
			//fmt.Printf("xxxline[0]===%c \n", line[0])
			if line[0] != '[' || line[len(line)-1] != ']' {
				err4 := fmt.Errorf("line:%d syntax error", idx+1)
				return err4
			}
			//把这一行首位的[]去掉，取到中间的内容，把空格去掉，拿到内容
			sectionName := strings.TrimSpace(line[1 : len(line)-1])
			if len(sectionName) == 0 {
				err4 := fmt.Errorf("line:%d syntax error", idx+1)
				return err4
			}
			//根据字符串sectionName去data里面根据反射找到对应的结构体

			for i := 0; i < t.Elem().NumField(); i++ {
				field := t.Elem().Field(i)
				//fmt.Println("field===", field)
				if sectionName == field.Tag.Get("ini") {
					//说明找到了对应了对应的嵌套结构体，把名字记下来
					structName = field.Name
					fmt.Printf("找到%s对应的嵌套结构体%s \n", sectionName, structName)
				}
			}
		} else {
			//2.3如果不是，开头就是=分割的键值对
			//1.等号左边是key，右边是value
			//if strings.Index(line, "=") == -1 || strings.HasPrefix(line, "=") {
			if strings.Index(line, "=") == -1 || strings.HasPrefix(line, "=") {
				err5 := fmt.Errorf("line:%d syntax error", idx+1)
				return err5
			}
			//fmt.Println("line===", line)
			index := strings.Index(line, "=")
			key := strings.TrimSpace(line[:index])
			value := strings.TrimSpace(line[index+1:])
			//2.根据structName去data里面把对应嵌套结构体取出来
			v := reflect.ValueOf(data)
			//fmt.Println("v===", v)
			sValue := v.Elem().FieldByName(structName) //嵌套结构值信息
			sType := sValue.Type()                     //嵌套结构值类型
			//fmt.Println("sValue===", sValue)
			//fmt.Println("sType===", sType)
			if sType.Kind() != reflect.Struct {
				err6 := errors.New("data should be a struct pointe") //新创建一个错误
				return err6
			}
			//3.遍历嵌套结构体的每一个字段，判断tag是不是等于key
			var fieldName string
			var fileType reflect.StructField
			for i := 0; i < sValue.NumField(); i++ {
				filed := sType.Field(i)
				//fmt.Println("flied===", filed)
				fileType = filed
				if filed.Tag.Get("ini") == key { //tag信息是存储在类型信息中
					//找到对应字段
					fieldName = filed.Name
					break
				}
			}

			//4.根据fieldName去取出这个值
			if len(fieldName) == 0 {
				//在结构体中找不到对应字段
				continue
			}
			fileObj := sValue.FieldByName(fieldName)
			//对齐赋值
			//fmt.Println(fieldName, fileObj, fileType, fileType.Type.Kind(), fileType.Type)

			switch fileType.Type.Kind() {
			case reflect.String:
				fileObj.SetString(value)
			case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
				valueInt, err7 := strconv.ParseInt(value, 10, 64)
				if err7 != nil {
					err7 := fmt.Errorf("line:%d syntax error", idx+1)
					return err7
				}
				fileObj.SetInt(valueInt)
			case reflect.Bool:
				valueBool, err8 := strconv.ParseBool(value)
				if err8 != nil {
					err8 := fmt.Errorf("line:%d syntax error", idx+1)
					return err8
				}
				fileObj.SetBool(valueBool)
			}
		}
	}

	return
}

func main() {
	var cfg Config
	//var x = new(int)
	err := loadIni("./conf.ini", &cfg)
	if err != nil {
		fmt.Printf("load ini failed,err:%v\n", err)
		return
	}
	fmt.Println(cfg)
}
